课程文档:http://docs.vikingship.xyz/,尚硅谷讲解vue3+ts文档:https://24kcs.github.io/vue3_study/

这里内容很多,但不是TS的全部,还有高级用法,在官方文档里面可以看。不过先将这些功能用起来就差不多了,面试就够了,面试的时候把各种用法说一下就行了,别人的问题不会超过这个文档的范围,如果超过了,回答不出来再说,实际应用时更复杂的用法等遇到了就去查找文档。

TS居然有汉语提示,我写了这么多种代码,就没见到过汉语提示,你说TS好不好用。(光学习的话会觉得这个typescript很好用,但是实际做项目的时候,定义起来是非常复杂的,所以要知道更重要的是逻辑,做项目就是要先把活做完,这种提示等到我真正做项目的时候,我是恨不得把这个提示关掉的,如果是自己定义的还好,如果是多人合作,就更加痛苦了,改别人的代码别人是会拼命的,所以痛苦)

image-20210220215833607

1、关于在项目中使用typescript的一些思考:

vue3对typescript的支持有了很大的提升,但是用起来即使是高手也有一些困扰,类型定义确实复杂,偶然间搜索到一个问答,里面讲了vue3对typescript的支持,有一个回答我觉得很好,能够解决我的一些心理负担。

image-20230403090529075

https://www.zhihu.com/question/453332049。尤雨溪亲自作答。这个还是要看项目规定,如果别人硬是要每个数据类型都定义清楚,那也没有办法,多花点时间去定义;如果不那么严格,就在难定义的地方使用any类型,先把业务逻辑走通,项目完成之后,可以花一些时间来专门定义类型。

2、使用typescript的难度层级

我自己总结一下使用typescript的难度层级,一级一级的递增。(这个就可以在面试的时候和别人聊一聊,就算说的不好,听一下别人的意见也是很好的,也许别人要的就是这样的真实的总结)

①简单地使用原始类型,定义一下number或string等数据类型,其余的一概用any类型。

②可以使用typescript来定义后端返回数据的类型。

③对第三方库的元素进行数据类型定义,这部分比较麻烦,因为很多不一定有文档,有了文档不一定有types库来安装,比如说echarts有文档,但别的第三方库不一定有。

 

第一章 TypeScript简介

什么是TypeScript?

image-20201218161341305

image-20201218172549011

image-20201225150224356

TypeScript相比JS优势:

image-20201218182722795

 

第二章 你好 Typescript: 进入类型的世界

2-1 安装 Typescript

Typescript 官网地址: https://www.typescriptlang.org/zh/

使用 nvm 来管理 node版本: https://github.com/nvm-sh/nvm

image-20201218183545322

安装Typescript:

2-1-1 执行 Typescript

执行ts文件:

image-20201218183342574

方式一:使用tsc全局命令,将ts文件转译为js文件后,执行js文件:

方式二:安装ts-node,直接执行ts文件:

ts文件在VSCODE中自动编译:

(webstorm上配置在webstrom快捷键.md文件中有)

image-20201225151237377

image-20201225151457117

image-20201225151940487

image-20201225152105842

image-20201225152146394

image-20201225152209102

image-20201225153139329

image-20201225163557334

2-1-2 使用webpack打包 Typescript

主要就是ts-loader来解析项目里面的ts代码,将其转换为js代码,其余的和js项目一样。

参考这里:https://blog.csdn.net/wu5229485/article/details/94721056

下载依赖

入口JS: src/main.ts

index页面: public/index.html

build/webpack.config.js

配置打包命令

运行与打包

2-1-3 typescript数据类型

Typescript 文档地址:Basic Types

Boolean Null Undefined Number String BigInt Symbol Void Array Tuple Enum Unknown Any Never Object

2-2 原始数据类型

TS中原始数据类型的值不可被修改为其它类型。

typescript原始数据类型 - primitive values:

2-2-1 boolean、number、string、undefined、null类型

2024.01.02

undefined和null是所有类型的子类型,但是如果直接赋值给number类型, 还是会有提示:

image-20240102145331884

typescript版本是5.3.2。可能原因是我使用的是typescript-playground,文件是tsx类型,而不是ts类型,可能会有部分不同。

2-2-2 any 类型

指定any(任意)类型,值可以被修改为任意类型。

2024.01.02

上面的 object[] 这种类型,可以设置为更具体的类型:


Array<Array<any>>,我知道这是泛型的指定类型的方式,但是这里的Array表示的是什么呢?从提示可以知道,这里的Array是interface:

image-20240102151120216

image-20240102151211195

2-2-3 void类型

表示没有任何类型,当一个函数没有返回值时,通常会见到其返回值是void。

2-2-4 object类型

表示非原始类型,也就是除numberstringboolean等之外的类型。使用object类型,就可以更好地表示像Object.create这样的API。

这里我发现一个问题,就是如果obj定义为object类型,在函数中使用obj.name和obj.age的话,会报错。

image-20230523093658831

不仅仅是标红,执行也会报错。原因是什么呢?因为是object类型里面没有name和age属性,所以不能使用。说明object类型不能在这种情况下使用。那么该怎么办呢?

使用接口。

image-20230523094007933

执行OK。

2-2-5 联合类型

表示取值可以为多种类型中的一种

需求1:定义一个函数,得到一个数字或字符串的字符串形式

需求2:定义一个函数,得到一个数字或字符串的长度

2024.01.02

注意:上面类型断言的第二种写法,不要和泛型的写法弄混了,眼睛一晃很容易弄混。

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

2-2章节其实就是数据类型的基础,有原始数据类型、any、void、object、联合数据类型,后面章节的数据类型其实可以称为“组合类型”,这个名称是我自己取的,本来就可以这么理解。

这一点明确了,后面的内容就好理解了,用起来就很方便了。

2-3 Array(数组) 和 Tuple(元组)

Typescript 文档地址:Array 和 Tuple

定义数组类型:

定义元组类型:

为什么要指定类型呢?因为后面的操作需要固定类型。那么别人在使用的时候,不需要等到运行阶段才发现错误,而是在编写代码的阶段就监视别人的使用状态,做出预警,这就提高了效率,免得别人到处找原因。
下面是一个反面例子。

2024.01.02

二维元组类型可以这样定义:

还是需要赋值的时候,类型和个数严格对应。

 

2-4 interface (接口)

Typescript 文档地址:Interface

Duck Typing 概念:

如果某个东西长得像鸭子,像鸭子一样游泳,像鸭子一样嘎嘎叫,那它就可以被看成是一只鸭子。

 

2-5 函数

Typescript 文档地址:Functions

函数类型:通过接口的方式作为函数的类型来使用。为了使用接口表示函数类型,需要给接口定义一个调用签名。它就像是一个只有参数列表和返回值类型的函数定义。参数列表里面的每一个参数都需要名字和类型。

image-20210224160530286

image-20210224161016158

注意:

如果使用接口或type为函数添加类型,需要写成匿名函数的形式,也就是函数表达式的形式。

函数重载

定义:函数名相同,而形参不同的多个函数。JS中没有函数重载,但TS和Java中存在此语法。

函数重载有什么好处呢?当用户用同一个函数名时,函数可以根据参数的不同返回不同的结果,这样就简化了代码。同时让别人在使用时,能够少记一些函数方法名,只需要根据不同的参数得到不同的结果,这样就简化了代码。

image-20240102165628811

如果我不想写函数重载,完全可以用类型断言来写,照样可以达到目的。

2-6 类型推论,联合类型,类型断言,类型守卫

类型推论 - type inference

https://www.typescriptlang.org/docs/handbook/type-inference.html

没有明确的指定类型的时候,推测出一个类型。

联合类型 - union types

https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-types

使用的时候,要使用联合类型的共同方法才能不报错,否则最好先判断类型再使用(这时候就要用到类型断言,否则会报错)。

类型断言 - type assertions

https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions

类型断言的第二种写法:

类型守卫 - type guard

https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types

就是使用typeofinstanceofin先判断类型,然后就不用写另外一种类型判断,TS会帮助我们判断。这是一种简便方法。

 

2-7 Class 类

面向对象编程的三大特点:

类 - Class

!!!!!!!!!!!!!!!!!!!!!!!!

JS中的类定义没有public、没有private、没有protected、没有readonly。

千万不要搞混了。

类的封装和继承

注意:在上面的代码中,实例化对象的时候,应该在对象后面指定类型,比如说const snake: Aniaml = new Animal('lily'),但是这个老师的文档里面没有写,这是TS的类型推论,并不代表可以不写,我最好还是写上去。

与JS定义类不同的地方

image-20210312154110660

这个提示仔细一读,这里虽然定义的是class但是ts将这个Animal类看作成了一种type,说明这就是和JS不一样的地方。

类的修饰符

类成员的访问修饰符

https://www.typescriptlang.org/docs/handbook/classes.html#public-private-and-protected-modifiers

作用:描述类中的成员(属性,构造函数,方法)的可访问性。

readonly修饰符

使用readonly修饰符将属性设置为只读,只读属性必须在声明时或构造函数里面被初始化。

image-20230506105024246

image-20210220220319211

回答上图中的问题:JS中可以这么写,但一般不需要预先对属性进行定义,而是直接在constructor里面进行定义。

 

类的多态:

父类型的引用指向了子类型的对象(就是说在创建子类型的实例时,使用父类型来修饰),不同类型的对象针对相同的方法,产生了不同的行为。

这里的重点是:

1、使用父类型来修饰子类型的实例;

2、子类型中定义了同名的方法;

3、子类的实例使用同名方法时,会执行定义在子类型中的方法。

其实这里大部分都是JS类的知识,只不过添加了一些ts类型的功能。

 

存取器

TS支持通过getters/setters来截取对对象成员的访问,它能帮助你有效的控制对对象成员的访问。

注意:get和set本身就是js定义的关键字,有特定的作用。和其余的关键字,比如说class、function的是类似的,多用就会用了。这些都是JS的知识。

 

静态成员

静态成员:在类中通过static修饰的属性或方法,就是静态的属性或方法,使用的时候通过类名.的方式来调用。这和js用法是一致的。

 

抽象类

抽象类作为其它派生类的基类(父类)使用,它们不能被实例化。不同于接口,抽象类可以包含成员的实现细节。abstract关键字用于定义抽象类和在抽象类中定义抽象方法。

 

2-8 类与接口

类本身可以当作一种类型,这里与接口结合,是为什么呢?从下面的例子中可以看到,接口其实是定义好了方法的类型,那么类与接口结合,就可以很简单地实现类中方法的类型定义,而不用另外写一遍。

类实现一个接口

这里的例子好像没有报错,new Car().switchRadio(1);在ts文件中并没有进行报错,不应该这样啊,虽然说Car这个类的switchRadio方法并没有规定类型,但已经implements Radio了,应该有类型提示啊,找原课程看一下。

上面这里的意思是说:

image-20240103101400772

执行new Person().switchRadio(1)没有报错,我的想法是不应该的。但实际上是可以的,因为ts已最具体的方法为主,虽然implements的Radio接口上参数有类型,但是实际定义的类方法上参数没有类型,那么使用implements的时候,代码提示都要求写完整的代码。

定义了Radio接口,好像唯一的好处就是,在写这个方法的时候,可以有提示直接写出来,但必须写完整,否则new Car().switchRadio(1);是不会有报错的。

是的,整个typescript不就是这个功能吗,让你写代码更规范。

如果使用了implements,则必须要在类里面写implements过来的方法,否则会报错。

image-20240103100550001

2024.01.03

那能不能直接在类中的方法上定义类型呢?而不是使用implements关键字?

可以,只不过接口里面不需要写方法名,并且类方法要写成匿名函数(函数表达式)的形式。

image-20240103102604781

写成命名函数的形式是不行的:

image-20240103103647909

 

2-9 枚举 Enums

https://www.typescriptlang.org/docs/handbook/enums.html

就像名字那样,是为了一个个的举例来使用的。

!!!!!!!!!!!!!!!!!!!!!!!!

ts不是讲类型吗?这个枚举是类型吗?很明显不是,在我看来这个只是一种附加功能。

 

2-10 泛型 Generics

1、基本用法

https://www.typescriptlang.org/docs/handbook/generics.html

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

这个方法让我们能够在使用的时候,清楚的知道我定义了什么类型,然后应该传入什么样类型的数据,这就非常灵活好用了。(但是定义起来非常复杂、麻烦)

这个不要和类型断言的第二种用法搞混了,类型断言是 <类型>变量名 ,而泛型里面是 函数名<T>

!!!!!!!!!!!!!!!!!!!!!!!!

仔细看一看,泛型定义和普通的函数(这里以函数来举例,其实还有类、接口等等都可以用到泛型)定义有什么区别呢?

1、泛型定义的函数,函数名后面加上了泛型定义,function 函数名(参数名: T): T{}。而普通函数的定义没有泛型定义,function 函数名(参数名: 数据类型): 数据类型{}

2、使用的时候,泛型定义的函数,需要在函数名后面指定数据类型,const result = echo(123),这时候ts会根据函数使用时定义的类型来提示。普通函数使用时,和JS一样。

image-20210224203652729

2024.01.02

上面这段话的第一行不准确:“这个不要和类型断言的第二种用法搞混了,类型断言是 <类型>变量名 ,而泛型里面是 函数名<T>”。

泛型里面不只是函数名<T>,还有接口<T>类<T>类型别名<T>这些写法,详见https://wangdoc.com/typescript/generics

 

image-20201226140900444

image-20210224201139544

上面这张图的注解有一个错误,就是泛型函数在使用的时候,可以在函数名后面加上泛型,也可以不加上泛型,如果不加,ts会使用类型推断。

箭头函数的泛型写法:

测试一下:

image-20240201134833447

image-20240201134847872

以后无论哪种写法,都要会。

泛型的符号T指代的含义其实很灵活,上面的只是最简单的例子,将T指定为number或者string类型,其实T完全可以指定为接口、另一个使用了泛型定义的类型、数组类型等等,这中用法一定要会:

image-20240201143757970

输出内容:

image-20240201143826506

只要我习惯看泛型的嵌套,ts的难度就会降低很多了。

2、泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法。定义一个接口来约束泛型。

从上一个小节的例子可以看到,定义的类型要么是直接返回参数,要么就是直接定义某种类型。没有涉及到在函数里面对泛型参数进行处理。

那么在函数里面对泛型参数进行处理,这时候T表示任意类型,太宽泛了,无法保证这个参数有你想要的属性,ts这时候就会提示Property 'xxx' does not exist on type 'T'.,这时候就需要使用泛型约束来收缩类型,这样ts就能够知道,使用你指定的属性是没有问题的。

案例:

上例中,泛型 T 不一定包含属性 length,我们在使用时可以给他传入任意类型,当然有些不包括 length 属性,那样就会报错。所以此时ts会提示我们Property 'length' does not exist on type 'Type'.

1、指定更加具体的类型

把类型改为T[],这就表示value一定存在length属性,因此就可以访问了。

2、使用 extends 来约束泛型

extends:延伸;扩充;继承。在ts里面有多种含义。

在 TypeScript(TS)中,extends 是核心关键字,核心含义围绕 “继承”“约束” 两大场景,具体用法完全贴合 TS 的类型系统特性,以下是最常用、最核心的 4 种含义(按使用频率排序):

1. 类(Class)继承:直接对应 “继承”(和 Java 逻辑一致)

这是最贴近 OOP 基础的用法,和你之前了解的 Java 中 extends 含义完全相同 —— 表示子类继承父类,子类会获得父类的属性、方法(可重写),且 TS 中类只能单继承(一个子类只能 extends 一个父类)。

示例代码:

2. 接口(Interface)扩展:对应 “扩展”(复用 + 新增类型约束)

TS 中接口(interface)用 extends 表示扩展已有接口—— 本质是 “复用原有接口的类型约束,再新增自己的约束”,支持单扩展或多扩展(多个接口用逗号分隔)。

示例代码:

3. 泛型(Generic)约束:对应 “约束 / 限定”(缩小泛型范围)

这是 TS 特有的核心用法 —— 用 extends 限制泛型的可选类型范围,确保泛型参数必须满足某个条件(比如继承某个类、实现某个接口),避免传入不合法的类型。

示例代码:

常见延伸:泛型条件类型(T extends U ? X : Y

extends 还用于 “类型三元表达式”,表示 “如果 T 满足 U 的约束,则返回类型 X,否则返回 Y”,是 TS 高级类型(如 ExcludeExtract)的实现基础。

示例:

4. 模块扩展(Module Augmentation):对应 “扩展”(补充模块类型)

TS 中可通过 extends 扩展已有模块 / 第三方库的类型定义(比如给 ArrayVue 等内置 / 第三方类型新增方法 / 属性),解决 “第三方库类型缺失” 的问题。

示例代码(扩展数组类型):

使用场景含义核心目的
类(Class)继承继承子类复用父类的属性 / 方法
接口(Interface)扩展扩展复用原有接口,新增类型约束
泛型(Generic)约束约束 / 限定缩小泛型的合法类型范围
泛型条件类型满足约束?基于类型约束判断返回目标类型
模块扩展(Module Aug)扩展补充已有模块的类型定义

这里的T extends IWithLength可以理解为“约束T必须为IWithLength类型或者其扩展类型”。这样理解就很清晰了。

image-20230506153316085

image-20230523150634713

3、多个泛型变量的情况

泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量的约束)。

比如,创建一个函数来获取对象中属性的值:

image-20251102185225737

4、泛型接口

泛型接口:在定义接口时,为接口中的属性或方法定义泛型类型,在使用接口时,再指定具体的泛型类型。

另外一个例子:

image-20251102190501691

数组就是泛型接口

这个只是说明ts中有这种功能,我在使用的时候注意一下即可,不需要我做什么。

JS的数组在TS中就是一个泛型接口,当我们在使用数组时,TS会根据数组的不同类型,来自动将类型变量设置为相应的类型。

比如说我定义了一个数组const strs = ['a','b','c'];,当写出strs.的时候,就会给出提示,能够使用哪些方法。

image-20251102191815753

并且,当我使用某个方法之后,可以将鼠标悬浮到这个方法上面,来查看这个方法的具体的类型定义。

image-20251102190847887

5、泛型类

泛型与类

与泛型定义函数一样,需要先在类上定义好类型:class 类名{},然后类里面可以使用泛型T。

2024.01.03

在定义泛型的时候,使用了T、U,脑袋还是很混乱,说明之前我学习react hooks的时候是没有搞清楚用法的。

image-20240103110751228

这里的用法很明显是在使用,所以没有看见T、U这些抽象符号。至于为什么这里可以使用泛型,肯定是看了文档的,这里指定了泛型,所以可以用。

6、泛型工具类型

TS内置了一些常用的工具类型,来简化TS中的一些常见的操作。

说明:它们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用。

这些工具类型有很多,现在只学习下面几个。

1、Partial<Type>

作用:用来构造(创建)一个类型,将Type的所有属性设置为可选。

构造出来的新类型PartialProps结构与Props相同,但所有属性都变为可选的。

image-20251102193504700

2、Readonly<Type>

作用:构造一个类型,将Type的所有属性都设置为readonly(只读)。

构造出来的ReadonlyProps结构与Props相同,但所有属性都变为只读的。

当我们想赋值时,就会报错:

image-20251102194206630

image-20251102194228170

3、Pick<Type, Keys>

从Type中选择一组属性来构造新类型。

image-20251102194629089

image-20251102194552092

4、Record<Keys, Type>

构造一个对象类型,属性键为Keys,属性类型为Type。

这句话要理解清楚,首先结果是生成一个对象类型,那么对象类型里面的key就从Keys里面来,那么key的类型全部为Type类型。看下面这个案例就清楚了。

那么RecordObj的类型结构就是下面这样:

image-20251102195131246

image-20251102195201945